using System;
using System.Collections.Generic;
using System.Reflection;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Microsoft.Practices.Unity.Interception
{
    [TestClass]
    public class OutParametersVirtualInterceptorTest
    {
        static T WrapAndCreateType<T>(IEnumerable<KeyValuePair<MethodBase, List<IInterceptionHandler>>> handlers,
                                      params object[] ctorArgs)
        {
            Type wrappedType = VirtualInterceptor.WrapClass(typeof(T));
            ILEmitProxy proxy = new ILEmitProxy(handlers);
            List<object> wrappedCtorArgs = new List<object> { proxy };
            wrappedCtorArgs.AddRange(ctorArgs);
            return (T)Activator.CreateInstance(wrappedType, wrappedCtorArgs.ToArray());
        }

        [TestMethod]
        public void OutComplexValueType()
        {
            RecordingHandler handler = new RecordingHandler();
            MethodBase method = typeof(SpyOut).GetMethod("OutComplexValueType");
            Dictionary<MethodBase, List<IInterceptionHandler>> dictionary = new Dictionary<MethodBase, List<IInterceptionHandler>>();
            List<IInterceptionHandler> handlers = new List<IInterceptionHandler> { handler };
            dictionary.Add(method, handlers);

            SpyOut result = WrapAndCreateType<SpyOut>(dictionary);
            ComplexValueType outValue;
            result.OutComplexValueType(out outValue);

            Assert.AreEqual(byte.MaxValue, outValue.Byte);
            Assert.AreEqual('a', outValue.Char);
            Assert.AreEqual(decimal.MaxValue, outValue.Decimal);
            Assert.AreEqual(double.MaxValue, outValue.Double);
            Assert.AreEqual(float.MaxValue, outValue.Float);
            Assert.AreEqual(int.MaxValue, outValue.Int);
            Assert.AreEqual(long.MaxValue, outValue.Long);
            Assert.AreEqual(short.MaxValue, outValue.Short);
            Assert.AreEqual("Hello, world!", outValue.String);
            Assert.AreEqual(uint.MaxValue, outValue.UInt);
            Assert.AreEqual(ulong.MaxValue, outValue.ULong);
            Assert.AreEqual(ushort.MaxValue, outValue.UShort);
        }

        [TestMethod]
        public void OutDouble()
        {
            RecordingHandler handler = new RecordingHandler();
            MethodBase method = typeof(SpyOut).GetMethod("OutDouble");
            Dictionary<MethodBase, List<IInterceptionHandler>> dictionary = new Dictionary<MethodBase, List<IInterceptionHandler>>();
            List<IInterceptionHandler> handlers = new List<IInterceptionHandler> { handler };
            dictionary.Add(method, handlers);

            SpyOut result = WrapAndCreateType<SpyOut>(dictionary);
            double outValue;
            result.OutDouble(out outValue);

            Assert.AreEqual(double.MaxValue, outValue);
        }

        [TestMethod]
        public void OutInt16()
        {
            RecordingHandler handler = new RecordingHandler();
            MethodBase method = typeof(SpyOut).GetMethod("OutInt16");
            Dictionary<MethodBase, List<IInterceptionHandler>> dictionary = new Dictionary<MethodBase, List<IInterceptionHandler>>();
            List<IInterceptionHandler> handlers = new List<IInterceptionHandler> { handler };
            dictionary.Add(method, handlers);

            SpyOut result = WrapAndCreateType<SpyOut>(dictionary);
            short outValue;
            result.OutInt16(out outValue);

            Assert.AreEqual(short.MaxValue, outValue);
        }

        [TestMethod]
        public void OutInt32()
        {
            RecordingHandler handler = new RecordingHandler();
            MethodBase method = typeof(SpyOut).GetMethod("OutInt32");
            Dictionary<MethodBase, List<IInterceptionHandler>> dictionary = new Dictionary<MethodBase, List<IInterceptionHandler>>();
            List<IInterceptionHandler> handlers = new List<IInterceptionHandler> { handler };
            dictionary.Add(method, handlers);

            SpyOut result = WrapAndCreateType<SpyOut>(dictionary);
            int outValue;
            result.OutInt32(out outValue);

            Assert.AreEqual(int.MaxValue, outValue);
        }

        [TestMethod]
        public void OutInt64()
        {
            RecordingHandler handler = new RecordingHandler();
            MethodBase method = typeof(SpyOut).GetMethod("OutInt64");
            Dictionary<MethodBase, List<IInterceptionHandler>> dictionary = new Dictionary<MethodBase, List<IInterceptionHandler>>();
            List<IInterceptionHandler> handlers = new List<IInterceptionHandler> { handler };
            dictionary.Add(method, handlers);

            SpyOut result = WrapAndCreateType<SpyOut>(dictionary);
            long outValue;
            result.OutInt64(out outValue);

            Assert.AreEqual(long.MaxValue, outValue);
        }

        [TestMethod]
        public void OutReferenceType()
        {
            RecordingHandler handler = new RecordingHandler();
            MethodBase method = typeof(SpyOut).GetMethod("OutReferenceType");
            Dictionary<MethodBase, List<IInterceptionHandler>> dictionary = new Dictionary<MethodBase, List<IInterceptionHandler>>();
            List<IInterceptionHandler> handlers = new List<IInterceptionHandler> { handler };
            dictionary.Add(method, handlers);

            SpyOut result = WrapAndCreateType<SpyOut>(dictionary);
            string outReference;
            result.OutReferenceType(out outReference);

            Assert.AreEqual("Hello, world!", outReference);
        }

        public class SpyOut
        {
            public virtual void OutChar(out char outValue)
            {
                outValue = 'a';
            }

            public virtual void OutComplexValueType(out ComplexValueType outValueType)
            {
                outValueType = new ComplexValueType
                                   {
                                       Byte = byte.MaxValue, 
                                       Char = 'a', 
                                       Decimal = decimal.MaxValue, 
                                       Double = double.MaxValue, 
                                       Float = float.MaxValue, 
                                       Int = int.MaxValue, 
                                       Long = long.MaxValue, 
                                       Short = short.MaxValue, 
                                       String = "Hello, world!", 
                                       UInt = uint.MaxValue, 
                                       ULong = ulong.MaxValue, 
                                       UShort = ushort.MaxValue
                                   };
            }

            public virtual void OutDouble(out double outValue)
            {
                outValue = double.MaxValue;
            }

            public virtual void OutInt16(out short outValue)
            {
                outValue = short.MaxValue;
            }

            public virtual void OutInt32(out int outValue)
            {
                outValue = int.MaxValue;
            }

            public virtual void OutInt64(out long outValue)
            {
                outValue = long.MaxValue;
            }

            public virtual void OutReferenceType(out string outReference)
            {
                outReference = "Hello, world!";
            }
        }
    }
}